/** Screen management and output classes.
*/

/** Represents a screen color.
@param r Red intensity, 0 - 1.
@param g Green intensity, 0 - 1.
@param b Blue intentisy, 0 - 1.
*/
/** Copy constructor form.
@param r Color to copy from.
*/
function Color(r, g, b)
{
	if(g === undefined)
	{
		this.r = r.r;
		this.g = r.g;
		this.b = r.b;
	}
	else
	{
		this.r = r;
		this.g = g;
		this.b = b;
	}
}
Color.prototype =
{

	/** Gets the CSS rgb() function string for this color.
	*/
	cssColorString: function()
	{
		return "rgb(" + Math.min(Math.max(Math.floor(this.r * 255), 0), 255) +
			", " + Math.min(Math.max(Math.floor(this.g * 255), 0), 255) +
			", " + Math.min(Math.max(Math.floor(this.b * 255), 0), 255) + ")";
	},
	/** Creates a new color that is this color alpha blended with another color.
	 *@param c Color to blend with.
	 *@param a Alpha value of c.
	 */
	alphaBlend: function(c, a)
	{
		return new Color(
			(1 - a) * this.r + a * c.r,
			(1 - a) * this.g + a * c.g,
			(1 - a) * this.b + a * c.b
		);
	},
	/** Creates a new color that is this color multiplied by some value.
	 *@param value Value to multiply by.
	 */
	newScaled: function(value)
	{
		return new Color(
			this.r * value,
			this.g * value,
			this.b * value
		);
	}
};
/** Create a new color that is interpolated between two colors.
 *@param c1 The color to interpolate from.
 *@param c2 The color to interpolate to.
 *@param weight The weight of the interpolation, from 0 to 1.
 */
Color.interpolate = function(c1, c2, weight)
{
	var ret = new Color(c1);
	ret.r += (c2.r - ret.r) * weight;
	ret.g += (c2.g - ret.g) * weight;
	ret.b += (c2.b - ret.b) * weight;
	return ret;
}
// Color presets
Color.black = new Color(0, 0, 0);
Color.darkRed = new Color(0.66, 0, 0);
Color.darkGreen = new Color(0, 0.66, 0);
Color.brown = new Color(0.66, 0.33, 0);
Color.darkBlue = new Color(0, 0, 0.66);
Color.purple = new Color(0.66, 0, 0.66);
Color.teal = new Color(0, 0.66, 0.66);
Color.gray = new Color(0.66, 0.66, 0.66);
Color.grey = Color.gray;
Color.darkGray = new Color(0.33, 0.33, 0.33);
Color.darkGrey = Color.darkGray;
Color.red = new Color(1, 0, 0);
Color.green = new Color(0, 1, 0);
Color.yellow = new Color(1, 1, 0);
Color.blue = new Color(0, 0, 1);
Color.magenta = new Color(1, 0, 1);
Color.cyan = new Color(0, 1, 1);
Color.white = new Color(1, 1, 1);
// ASCII table of colors, yay!
Color.asciiCodes =
{
	'0': Color.black,
	'1': Color.darkRed,
	'2': Color.darkGreen,
	'3': Color.brown,
	'4': Color.darkBlue,
	'5': Color.purple,
	'6': Color.teal,
	'7': Color.gray,
	'8': Color.darkGray,
	'9': Color.red,
	'a': Color.green,
	'b': Color.yellow,
	'c': Color.blue,
	'd': Color.magenta,
	'e': Color.cyan,
	'f': Color.white,
	'g': null,
	'A': Color.green,
	'B': Color.yellow,
	'C': Color.blue,
	'D': Color.magenta,
	'E': Color.cyan,
	'F': Color.white,
	'G': null
};

/** Represents a screen cell.
@param c Character to display.
@param fg Foreground color.
@param bg Background color.
*/
function ScreenCell(c, fg, bg)
{
	this.c = c;
	this.fg = fg;
	this.bg = bg;
	this.changed = true;
}
ScreenCell.prototype =
{
};

/** Represents a buffer for screen output.
@param width Width in cells.
@param height Height in cells.
*/
function ScreenBuffer(width, height)
{
	this.width = width;
	this.height = height;
	this._cells = [];
	for(var i = 0; i < width * height; ++i)
	{
		this._cells.push(new ScreenCell(" ", new Color(Color.white), new Color(Color.black)));
	}
	this._stringFgColor = Color.white;
	this._stringBgColor = Color.black;
}
ScreenBuffer.prototype =
{
	/** Sets attributes about a cell. All parameters are optional but must be
	replaced by null.
	@param x X position.
	@param y Y position.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	setCell: function(x, y, c, fg, bg)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		var cell = this._cells[y * this.width + x];
		if(c !== null)
			cell.c = c;
		if(fg !== null)
			cell.fg = fg;
		if(bg !== null)
			cell.bg = bg;
		cell.changed = true;
	},
	/** Gets the cell at the specified location, or null if out of range.
	 *@param x X position;
	 *@param y Y position;
	 */
	getCell: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return this._cells[y * this.width + x];
	},
	/** Draws a string of ASCII text to the buffer, ignoring any character
	outside the range 32-127.
	

	Special text color escapements are supported in the form of "%XX", where X
	is a hex digit of one of the standard ASCII color values (see
	Color.asciiCodes). The first X sets the foreground color, the second the
	background color. Use "%%" for a percent sign.
	
	@param x X location.
	@param y Y location.
	@param str The string to blit.
	@param maxWidth The maximum width of the string to actually blit (optional). If specified this will clear to the end of the width.
	*/
	blitString: function(x, y, str, maxWidth)
	{
		var cx = 0, i, state = 0, c, code, temp;
		
		for(i = 0; i < str.length; ++i)
		{
			c = str.charAt(i);
			code = c.charCodeAt(0);
			if(code < 32 ||
				code > 127)
				continue;
			switch(state)
			{
				case 0:
					if(c == "%")
						state = 1;
					else
					{
						if(maxWidth === undefined ||
							cx < maxWidth)
							this.setCell(x + cx, y, c, this._stringFgColor, this._stringBgColor);
						cx++;
					}
					break;
				case 1:
					temp = Color.asciiCodes[c];
					if(temp !== undefined)
						this._stringFgColor = temp;
					state = 2;
					break;
				case 2:
					temp = Color.asciiCodes[c];
					if(temp !== undefined)
						this._stringBgColor = temp;
					state = 0;
					break;
			}
		}
		if(maxWidth !== undefined)
		{
			for(; cx < maxWidth; ++cx)
			{
				this.setCell(x + cx, y, " ", Color.white, Color.black);
			}
		}
	},
	/** Draws a rectangle with the specified attributes.
	@param x X position.
	@param y Y position.
	@param width Width.
	@param height Height.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	blitRect: function(x, y, width, height, c, fg, bg)
	{
		for(iy = y; iy < y + height; ++iy)
		{
			for(ix = x; ix < x + width; ++ix)
			{
				this.setCell(ix, iy, c, fg, bg);
			}
		}
	},
	/** Draws a box.
	@param x X position of box.
	@param y Y position of box.
	@param width Width of box.
	@param height Height of box.
	@param c Character to draw.
	@param fg Foreground color.
	@param bg Background color.
	*/
	drawBox: function(x, y, width, height, c, fg, bg)
	{
		// Horizontal lines
		this.blitRect(x, y, width, 1, c, fg, bg);
		this.blitRect(x, y + height - 1, width, 1, c, fg, bg);
		
		// Vertical lines
		this.blitRect(x, y + 1, 1, height - 2, c, fg, bg);
		this.blitRect(x + width - 1, y + 1, 1, height - 2, c, fg, bg);
	},
	/** Draws a menu box around )on the outside of) a given area.
	@param x X position of area.
	@param y Y position of area.
	@param width Width of area.
	@param height Height of area.
	*/
	drawMenuBox: function(x, y, width, height)
	{
		// Horizontal lines
		this.blitRect(x - 1, y - 1, width + 2, 1, " ", null, Color.white);
		this.blitRect(x - 1, y + height, width + 2, 1, " ", null, Color.white);
		
		// Vertical lines
		this.blitRect(x - 1, y, 1, height, " ", null, Color.white);
		this.blitRect(x + width, y, 1, height, " ", null, Color.white);
		
		// Drop shadown
		this.blitRect(x, y + height + 1, width + 2, 1, " ", null, Color.gray);
		this.blitRect(x + width + 1, y, 1, height + 1, " ", null, Color.gray);
	},
	/** Draws every cell in the buffer.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	clear: function(c, fg, bg)
	{
		this.blitRect(0, 0, this.width, this.height, c, fg, bg);
	},
	/** Copies an area of the buffer to another area. These areas may overlap.
	 *@param x X position of area to copy.
	 *@param y Y position of area to copy.
	 *@param width Width of area to copy.
	 *@param height Height of area to copy.
	 *@param dx Destination X position to copy to.
	 *@param dy Destination Y position to copy to.
	 */
	copyRectTo: function(x, y, width, height, dx, dy)
	{
		var ofs = 0;
		var buffer = [];
		var ix, iy, cell;
		for(iy = y; iy < y + height; ++y)
		{
			for(ix = x; ix < x + width; ++x)
			{
				cell = this._cells[iy * this.width + ix];
				buffer.push(new ScreenCell(cell.c, cell.fg, cell.bg));
			}
		}
		for(iy = y; iy < y + height; ++y)
		{
			for(ix = x; ix < x + width; ++x)
			{
				cell = buffer[ofs++];
				this.setCell(ix, iy, cell.c, cell.fg, cell.bg);
			}
		}
	},
	/** Pushes the contents of the buffer to another buffer
	@param dest Destination buffer.
	@param x X offset.
	@param y Y offset.
	@param force Blit even non-changed cells to the screen.
	*/
	blitTo: function(dest, x, y, force)
	{
		var ix, iy, ofs = 0, cell;
		
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				cell = this._cells[ofs++];
				if(cell.changed ||
					force)
				{
					dest.setCell(ix + x, iy + y, cell.c, cell.fg, cell.bg);
					cell.changed = false;
				}
			}
		}
	}
};
